import type { AObject } from "./AObject";
import type { Agent } from "./Agent";
import * as Debug from "./Debug";
import { AHistory, type Direction, type DestinationData, type Descriptor, type StatusDescriptor, type NullableDescriptor, type ScheduledRoutine, RoutineResult } from "./Definitions";
import { N } from "./NarrativeBase";

export class AZone {
    constructor(name: string, alias: string[], args?: Partial<AZone>) {
        if (args != undefined) Object.assign(this, args);
        this.name = name;
        this.alias = alias;
    }

    name: string = "[未定义]";
    alias: string[] = [];
    parent: AZone | null = null;
    subZones: AZone[] = [];
    objects = new Set<AObject>;
    agents = new Set<Agent>;
    history = new AHistory(this);

    isLit = false;
    temperature = 20;

    passages: (dir: Direction) => DestinationData | undefined = () => undefined;
    exitDirection = (): Direction | null => null;
    exitDoor = (): AObject | null => null;

    beforeGoDirection?: 
        (self: AZone, ag: Agent, dir: Direction, run: boolean) => ScheduledRoutine;

    extendedObjects = () => new Set<AObject>(this.objects);
    unclearObjects = () => new Set<AObject>;

    description: Descriptor<AZone> = 
        (self, ag) => (self.parent?.description(self, ag) ?? N.text('???'));
    status: StatusDescriptor<AZone> = () => null;

    // ADV_TODO: change this to something like AObject's dict of handlers
    onSmell: NullableDescriptor<AZone> = () => null;
    onListen: NullableDescriptor<AZone> = () => null;
    onAgentEnter: NullableDescriptor<AZone> = () => null;

    /** Note: does not include Agents' possessions */
    readonly hasObject = (name: string, recursive = true) => {
        return this.findObjectInternal(name, recursive) != null;
    };

    readonly findObjectInternal = (name: string, recursive = true) => {
        for (let x of this.extendedObjects())
            if (x.name.value == name || x.name.alias.has(name))
                return x;
            else if (recursive) {
                let y = x.findSubObjectInternal(name);
                if (y != null) return y;
            }
        return null;
    };

    readonly addObjectInternal = (...objs: AObject[]) => {
        for (let obj of objs) {
            Debug.assert(obj.parentContainer == null);
            this.objects.add(obj);
            obj.parentContainer = this;
        }
    };

    readonly removeObjectInternal = (...objs: AObject[]) => {
        for (let obj of objs) {
            Debug.assert(obj.parentContainer == this);
            this.objects.delete(obj);
            obj.parentContainer = null;
        }
    };

    readonly removeObject = (ag: Agent, obj: AObject): RoutineResult => {
        Debug.assert(obj.parentContainer == this);
        this.objects.delete(obj);
        obj.parentContainer = null;
        return RoutineResult.Continue();
    };

    readonly addObject = (ag: Agent, obj: AObject): RoutineResult => {
        Debug.assert(obj.parentContainer == null);
        this.objects.add(obj);
        obj.parentContainer = this;
        return RoutineResult.Continue();
    };
}
